<template>
  <div
    ref="floatBallRef"
    class="cjui-float-ball"
    @mousedown="mousedownEvent"
    @mouseenter="mouseenterEvent"
    @mouseleave="mouseleaveEvent"
  >
    <div class="cjui-float-ball-content" @mousedown.stop>
      <slot></slot>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

defineOptions({
  name: 'CjuiFloatBall'
})

const props = defineProps({
  boundaryTop: {  // 上边界安全距离px
    type: Number,
    default: 0
  },
  boundaryRight: {  // 右边界安全距离px
    type: Number,
    default: 0
  },
  boundaryBottom: {  // 下边界安全距离px
    type: Number,
    default: 0
  },
  boundaryLeft: {  // 左边界安全距离px
    type: Number,
    default: 0
  },
  position: {  // 初始位置 right-bottom | left-bottom
    type: String,
    default: 'right-bottom'
  },
  adsorbauto: {  // 边界自动依附 false | true | auto | left | right
    type: [Boolean, String],
    default: false
  },
  adsorbGutter: {  // 靠近边界时自动吸附距离
    type: Number,
    default: 10
  },
  halfHidden: {  // 边界时是否隐藏半边
    type: Boolean,
    default: false
  }
});
const emits = defineEmits<{
  isHalfHiddenChange: [value: boolean];
  posRangeChange: [value: string];
}>()

// 浮动球容器信息
const floatBallRef = ref();
const floatBallState = {
  width: 0, // 元素宽度
  height: 0, // 元素高度
  left: 0, // 位置-距左边界
  top: 0, // 位置-距顶部边界
  isHalfHidden: false, // 是否边界隐藏
  posRange: '', // 所在区域 left-左侧区域 | right-右侧区域
}
const getFloatBallInfo = () => {
  floatBallState.width = floatBallRef.value.offsetWidth;
  floatBallState.height = floatBallRef.value.offsetHeight;
}
const ro = new ResizeObserver(_entries => getFloatBallInfo())
const transitionValue = 'top .5s, left .5s, transform .5s'

// 初始化
const init = () => {
  getFloatBallInfo()

  // 初始化位置
  floatBallRef.value.style.transition = '';
  if (props.position === 'right-bottom') {
    move(floatBallRef.value, props.boundaryRight, 'right')
    move(floatBallRef.value, props.boundaryBottom + 100, 'bottom')
    
  } else if (props.position === 'left-bottom') {
    move(floatBallRef.value, props.boundaryLeft, 'left')
    move(floatBallRef.value, props.boundaryBottom + 100, 'bottom')
  } else {}

  // 判断所在区域
  setPosRange()

  // 边界隐藏
  setHalfHidden()

  // 设置默认过渡
  floatBallRef.value.style.transition = transitionValue;
}

// 元素操作方法
const move = (obj: HTMLElement, distance: number, direction: 'top' | 'right' | 'bottom' | 'left') => {
  if (direction === 'top') {
    obj.style.top = distance + 'px';
  } else if (direction === 'right') {
    obj.style.left = document.body.offsetWidth - floatBallState.width - distance + 'px';
  } else if (direction === 'bottom') {
    obj.style.top = document.body.offsetHeight - floatBallState.height - distance + 'px';
  } else if (direction === 'left') {
    obj.style.left = distance + 'px';
  }
}
const rate = (obj: HTMLElement, a: string | number) => {
  obj.style.transform = 'rotate(' + a + ')'
}

// 元素拖动
let cuntL = 0; // 鼠标点击元素时距元素左边界距离
let cuntT = 0; // 鼠标点击元素时距元素上边界距离
let mousedown = false; // 鼠标点击状态
let timer: any = null
const mouseenterEvent = () => {
  clearTimeout(timer)
  setTimeout(() => {
    restoreHalfHidden()
  }, 200)
}
const mouseleaveEvent = () => {
  clearTimeout(timer)
  setTimeout(() => {
    setHalfHidden()
  }, 500)
}
const mousedownEvent = (e: MouseEvent) => {
  e.preventDefault()
  mousedown = true
  cuntL = e.clientX - floatBallRef.value.offsetLeft;
  cuntT = e.clientY - floatBallRef.value.offsetTop;
  document.onmousemove = mousemoveEvent;
}
const mousemoveEvent = (e: MouseEvent) => {
  e.preventDefault()
  floatBallRef.value.style.transition = '';
  move(floatBallRef.value, e.clientX - cuntL, 'left')
  move(floatBallRef.value, e.clientY - cuntT, 'top')

  // 边界安全距离限制
  if (e.clientY - cuntT < props.adsorbGutter + props.boundaryTop) {
    move(floatBallRef.value, props.boundaryTop, 'top');
  }
  if (e.clientX - cuntL > document.body.offsetWidth - floatBallState.width - props.adsorbGutter - props.boundaryRight) {
    move(floatBallRef.value, props.boundaryRight, 'right');
  }
  if (e.clientY - cuntT > document.body.offsetHeight - floatBallState.height - props.adsorbGutter - props.boundaryBottom) {
    move(floatBallRef.value, props.boundaryBottom, 'bottom');
  }
  if (e.clientX - cuntL < props.adsorbGutter + props.boundaryLeft) {
    move(floatBallRef.value, props.boundaryLeft, 'left');
  }

  // 判断所在区域
  setPosRange()
}
const mouseupEvent = () => {
  document.onmousemove = null;
  if (mousedown) {
    mousedown = false
    setEndPosition();
  }
}
const setPosRange = () => {
  let oldVal = floatBallState.posRange
  if (floatBallRef.value.offsetLeft > (document.body.offsetWidth + floatBallState.width) / 2) {
    floatBallState.posRange = 'right'
  } else {
    floatBallState.posRange = 'left'
  }

  if (floatBallState.posRange !== oldVal) {
    emits('posRangeChange', floatBallState.posRange)
  }
}
const setEndPosition = () => {
  floatBallRef.value.style.transition = transitionValue;

  // 边界安全距离判断
  if (floatBallRef.value.offsetTop < props.boundaryTop) {
    move(floatBallRef.value, props.boundaryTop, 'top');
  }
  if (floatBallRef.value.offsetLeft > document.body.offsetWidth - floatBallState.width - props.boundaryRight) {
    move(floatBallRef.value, props.boundaryRight, 'right');
  }
  if (floatBallRef.value.offsetTop > document.body.offsetHeight - floatBallState.height - props.boundaryBottom) {
    move(floatBallRef.value, props.boundaryBottom, 'bottom');
  }
  if (floatBallRef.value.offsetLeft < props.adsorbGutter + props.boundaryLeft) {
    move(floatBallRef.value, props.boundaryLeft, 'left');
  }

  // 判断所在区域
  setPosRange()

  // 吸附判断
  if (props.adsorbauto === true || props.adsorbauto === 'auto') {
    if (floatBallState.posRange === 'right') {
      move(floatBallRef.value, props.boundaryRight, 'right');
    } else if (floatBallState.posRange === 'left') {
      move(floatBallRef.value, props.boundaryLeft, 'left');
    }
  } else if (props.adsorbauto === 'right') {
    move(floatBallRef.value, props.boundaryRight, 'right');
  } else if (props.adsorbauto === 'left') {
    move(floatBallRef.value, props.boundaryLeft, 'left');
  }

  // 边界隐藏
  setTimeout(() => {
    setHalfHidden();
  }, 550)
}
const setIsHalfHidden = (val: boolean) => {
  floatBallState.isHalfHidden = val
  emits('isHalfHiddenChange', floatBallState.isHalfHidden)
}
const restoreHalfHidden = () => {
  if (!floatBallState.isHalfHidden) {
    return
  }

  floatBallRef.value.style.transition = transitionValue;
  rate(floatBallRef.value, '0');
  if (floatBallState.posRange === 'right') {
    move(floatBallRef.value, 0, 'right')
  } else if (floatBallState.posRange === 'left') {
    move(floatBallRef.value, 0, 'left')
  }
  setIsHalfHidden(false)
}
const setHalfHidden = () => {
  if (!props.halfHidden || mousedown) {
    return
  }

  floatBallRef.value.style.transition = transitionValue;
  if (floatBallState.posRange === 'right' && floatBallRef.value.offsetLeft === document.body.offsetWidth - floatBallState.width) {
    rate(floatBallRef.value, '-90deg');
    move(floatBallRef.value, 0 - floatBallState.width / 2, 'right')
    setIsHalfHidden(true)
  } else if (floatBallState.posRange === 'left' && floatBallRef.value.offsetLeft === 0) {
    rate(floatBallRef.value, '90deg');
    move(floatBallRef.value, 0 - floatBallState.width / 2, 'left')
    setIsHalfHidden(true)
  }
}

watch(() => props.position, () => {
  init()
})
watch(
  () => [
    props.boundaryTop,
    props.boundaryRight,
    props.boundaryBottom,
    props.boundaryLeft,
    props.adsorbauto,
  ], 
  () => {
    restoreHalfHidden()
    setEndPosition()
  },
  { deep: true }
)
watch(() => props.halfHidden, (newVal) => {
  if (newVal) {
    setHalfHidden()
  } else {
    restoreHalfHidden()
  }
})

onMounted(() => {
  ro.observe(floatBallRef.value)
  init();
  window.addEventListener('resize', init);
  window.addEventListener('mouseup', mouseupEvent);
})

onBeforeUnmount(() => {
  ro.disconnect()
  window.removeEventListener('resize', init);
  window.removeEventListener('mouseup', mouseupEvent);
})
</script>

<style lang="scss">
.cjui-float-ball {
  width: 100px;
  height: 100px;
  color: #fff;
  background-color: rgba(0, 0, 0, 0.8);
  border: 4px solid rgba(255, 255, 255, 0.8);
  box-sizing: border-box;
  border-radius: 50%;
  cursor: move;
  position: fixed;
  z-index: 2000;
  display: flex;
  justify-content: center;
  align-items: center;
}

.cjui-float-ball-content {
  cursor: pointer;
}
</style>
